﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;

using System.Linq;

using ProjectBase.Core;

using $prjsn$.Common;
using $prjsn$.Common.Validation;

namespace $safeprojectname$
{
    public class OrderByExistingPropertiesRule<TFilter, TID, TEntity> : RuleBase, IValidationRule<TFilter>
        where TFilter : class, IFilter<TID>
        where TEntity : class, IPersistentObjectWithTypedId<TID>
    {
        protected const string Clause = "ORDER BY";
        protected const char DotSeparator = '.';
        private IMetaDataProvider<TEntity, TID> _metaDataProvider;

        public OrderByExistingPropertiesRule(IMetaDataProvider<TEntity, TID> metaDataProvider)
        {
            ValidateOn = ValidateOn.Find;
            _metaDataProvider = metaDataProvider;
            PropertyName = "OrderBy";
        }

        public virtual ValidationResult Validate(TFilter filter, out ValidationMessage validationMessage)
        {
            var messages = new List<ValidationMessage>();
            var metaData = MetaDataProvider.GetMetaData();

            var newOrderBy = new Dictionary<string, bool>();

            foreach (var pair in filter.OrderBy)
            {
                var name = pair.Key;
                var value = pair.Value;

                var message = name.Contains(DotSeparator)
                    ? CheckInnerPropertiesChain(name, value, newOrderBy, metaData)
                    : CheckProperty(name, value, newOrderBy, metaData);

                if (message.Is())
                {
                    messages.Add(message);
                }
            }

            if (messages.IsEmpty())
            {
                validationMessage = null;
                filter.OrderBy.Clear();
                foreach (var pair in newOrderBy)
                {
                    filter.OrderBy.Add(pair);
                }
                return ValidationResult.IsValid;
            }

            validationMessage = new ValidationMessage(Str.Messages.ValidationIncorrectNames1, Severity.Warning, messages)
            {
                Clause
            };
            return ValidationResult.Error;
        }

        #region Select with '.'

        protected virtual ValidationMessage CheckInnerPropertiesChain(string name, bool value
            , IDictionary<string, bool> newOrderBy
            , MetaDataMessage metaDataMessage)
        {
            var rootName = name.Split(DotSeparator).First();

            var attribute = metaDataMessage
                .Attributes
                .FirstOrDefault(md => md.Name.IsEqual(rootName));

            if (attribute.IsNull())
            {
                return new ValidationMessage(Str.Messages.ValidationPropertyNameNotFound3) { rootName, name, Clause };
            }

            if (!attribute.CanRead)
            {
                return new ValidationMessage(Str.Messages.ValidationInsufficientRights2) { attribute.Name, Clause };
            }

            var property = attribute.MappingInfo.PropertyInfo;
            var type = property.PropertyType;

            if (attribute.IsAdditional)
            {
                return CheckAdditional(name, newOrderBy, value, attribute);
            }

            // The Array like 'Addresses' cannot be used on a list
            var isArray = type.HasTypeListOf<IPersistentObject>();
            if (isArray)
            {
                return new ValidationMessage(Str.Messages.ValidationInnerPropertyMustBeValue3) { attribute.Name, name, Clause };
            }

            return CheckPropertyChain(name, newOrderBy, value, attribute);
        }

        protected virtual ValidationMessage CheckPropertyChain
            (string name
            , IDictionary<string, bool> newOrderBy
            , bool value
            , MDAttribute metaData)
        {
            var path = name.Split(DotSeparator);
            var newName = metaData.Name;

            var message = DeepChainCheck(name, metaData.Type, path.Skip(1).ToList(), ref newName);

            if (message.Is())
            {
                return message;
            }

            newOrderBy[newName] = value;
            return null;
        }

        protected virtual ValidationMessage CheckAdditional
            (string name
            , IDictionary<string, bool> newOrderBy
            , bool value
            , MDAttribute metaData)
        {
            // Additional.Department.Code
            var path = name.Split(DotSeparator);

            var propertyName = path[1];

            // Additional
            var newName = metaData.Name;

            var attribute = metaData
                .Attributes
                .FirstOrDefault(attr => attr.Name.IsEqual(propertyName));

            // no mapping - WRONG
            if (attribute.IsNull())
            {
                return new ValidationMessage(Str.Messages.ValidationInnerPropertyNotFound3) { propertyName, name, Clause };
            }

            if (!attribute.CanRead)
            {
                return new ValidationMessage(Str.Messages.ValidationInsufficientRights2) { attribute.Name, Clause };
            }

            newName += DotSeparator + attribute.Name;

            // Additional.Department.Code
            var isPersistent = attribute.Type.HasType<IPersistentObject>();

            // Additonal.Salary
            if (path.Length == 2)
            {
                // Additional.Department - missing ValueType property - WRONG
                if (isPersistent)
                {
                    return new ValidationMessage(Str.Messages.ValidationInnerPropertyMustBeValue3) { propertyName, name, Clause };
                }
                newOrderBy[newName] = value;
                return null;
            }

            // Additional.BirthDate.Code - WRONG
            if (!isPersistent)
            {
                var deepName = path[2];
                return new ValidationMessage(Str.Messages.ValidationInnerPropertyNotFound3)
                {
                    deepName,
                    name,
                    Clause,
                };
            }

            // the deep chain: Additional.Department.Parent.Code
            var message = DeepChainCheck(name, attribute.Type, path.Skip(2).ToList(), ref newName);

            if (message.Is())
            {
                return message;
            }

            newOrderBy[newName] = value;
            return null;
        }

        protected virtual ValidationMessage DeepChainCheck(string name
            , Type property, IList<string> propertyChain
            , ref string newName)
        {
            foreach (var partialName in propertyChain)
            {
                var propertyInfo = property.GetProperties().FirstOrDefault(p => p.Name.IsEqual(partialName));

                if (propertyInfo.IsNull())
                {
                    return new ValidationMessage(Str.Messages.ValidationInnerPropertyNotFound3) { partialName, name, Clause };
                }

                property = propertyInfo.PropertyType;

                var isPersistent = property.HasType<IPersistentObject>();
                var isLast = propertyChain.Last().IsEqual(partialName);

                if (isLast)
                {
                    if (property.IsNull())
                    {
                        return new ValidationMessage(Str.Messages.ValidationInnerPropertyNotFound3) { partialName, name, Clause };
                    }
                    if (isPersistent)
                    {
                        return new ValidationMessage(Str.Messages.ValidationInnerPropertyMustBeValue3) { partialName, name, Clause };
                    }

                    newName += DotSeparator + propertyInfo.Name;
                    break;
                }

                if (!isPersistent)
                {
                    return new ValidationMessage(Str.Messages.ValidationUnknownProperty3) { partialName, name, Clause };
                }

                newName += DotSeparator + propertyInfo.Name;
            }
            return null;
        }

        #endregion Select with '.'

        protected virtual ValidationMessage CheckProperty(string name, bool value
            , IDictionary<string, bool> newOrderBy
            , MetaDataMessage metaDataMessage)
        {
            var attribute = metaDataMessage
               .Attributes
               .FirstOrDefault(md => md.Name.IsEqual(name));

            if (attribute.IsNull())
            {
                return new ValidationMessage(Str.Messages.ValidationPropertyNameNotFound2) { name, Clause };
            }

            if (!attribute.CanRead)
            {
                return new ValidationMessage(Str.Messages.ValidationInsufficientRights2) { attribute.Name, Clause };
            }

            // Country
            var isPersistent = attribute.Type.HasType<IPersistentObject>();

            // missing ValueType property e.g. Country.Code
            if (isPersistent)
            {
                return new ValidationMessage(Str.Messages.ValidationInnerPropertyMustBeValue3) { attribute.Name, name, Clause };
            }

            var isArray = attribute.Type.HasTypeListOf<IPersistentObject>();
            if (isArray)
            {
                return new ValidationMessage(Str.Messages.ValidationCannotSelectArray3) { attribute.Name, name, Clause };
            }

            var newName = attribute.Name;
            newOrderBy[newName] = value;
            return null;
        }

        protected IMetaDataProvider<TEntity, TID> MetaDataProvider
        {
            get { return _metaDataProvider; }
            set { _metaDataProvider = value; }
        }
    }
}